Frisbee
:heart: Love this project? Support @niftylettuce's FOSS on Patreon or PayPal :unicorn:
Modern fetch-based alternative to axios/superagent/request. Great for React Native.
New in v2.0.4++: baseURI
is now optional and you can pass raw: true
as a global or request-based option to get the raw fetch()
response (e.g. if you want to use res.arrayBuffer()
or any other method manually).
Table of Contents
Install
Node (Koa, Express, React Native, ...)
-
Install the required package:
npm install --save frisbee
-
See usage example and API below
Browser
VanillaJS
- Load the package via
<script>
tag (note you will need to polyfill with required features):
<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js?features=es6,Array.from,Object.getOwnPropertyDescriptors,Object.getOwnPropertySymbols,Promise,Promise.race,Promise.reject,Promise.resolve,Reflect,Symbol.for,Symbol.iterator,Symbol.prototype,Symbol.species,Symbol.toPrimitive,Symbol.toStringTag,Uint8Array"></script>
<script src="https://unpkg.com/frisbee"></script>
<script type="text/javascript">
(function() {
var api = new Frisbee({
baseURI: 'https://api.startup.com',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
api.get('/hello-world').then(console.log).catch(console.error);
})();
</script>
- See usage example and API below for a more complete example.
Bundler
-
Install the required package:
npm install frisbee
-
Ensure that your environment is polyfilled with required features (e.g. use @babel/polyfill globally or a service like polyfill.io)
-
See usage example and API below
Usage
Example
const Frisbee = require('frisbee');
const api = new Frisbee({
baseURI: 'https://api.startup.com',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
api.get('/hello-world').then(console.log).catch(console.error);
(async () => {
try {
let res = await api.post('/v1/login');
if (res.err) throw res.err;
api.auth(res.body.api_token);
res = await api.post('/v1/messages', { body: 'Hello' });
if (res.err) throw res.err;
res = await api.get('/v1/messages', {
body: {
limit: 10,
page: 2
}
});
if (res.err) throw res.err;
res = api.post('/v1/logout');
if (res.err) throw res.err;
api.auth();
} catch (err) {
console.error(err);
}
})();
API
const Frisbee = require('frisbee');
Frisbee
is a function that optionally accepts an argument options
, which is an object full of options for constructing your API instance.
Upon being invoked, Frisbee
returns an object with the following chainable methods:
-
api.auth(creds)
- helper function that sets BasicAuth headers, and it accepts user
and pass
arguments
- You can pass
creds
user and pass as an array, arguments, or string: ([user, pass])
, (user, pass)
, or ("user:pass")
, so you shouldn't have any problems! - If you don't pass both
user
and pass
arguments, then it removes any previously set BasicAuth headers from prior auth()
calls - If you pass only a
user
, then it will set pass
to an empty string ''
) - If you pass
:
then it will assume you are trying to set BasicAuth headers using your own user:pass
string - If you pass more than two keys, then it will throw an error (since BasicAuth only consists of
user
and pass
anyways)
-
api.setOptions(opts)
- helper function to update instance options (note this does not call api.auth
internally again even if opts.auth
is passed)
-
api.jwt(token)
- helper function that sets a JWT Bearer header. It accepts the jwt_token
as a single string argument. If you simply invoke the function null
as the argument for your token, it will remove JWT headers.
-
api.abort(token)
- aborts all current/queued requests that were created using token
-
api.abortAll()
- aborts all current/queued - i.e. await
-ing in an interceptor - requests
-
All exposed HTTP methods return a Promise, and they require a path
string, and accept an optional options
object:
-
Accepted method arguments:
-
path
required - the path for the HTTP request (e.g. /v1/login
, will be prefixed with the value of baseURI
if set)
-
options
optional - an object containing options, such as header values, a request body, form data, or a querystring to send along with the request. These options by default are inherited from global options passed to new Frisbee({ options })
. For the GET
method (and the DELETE
method as of version 1.3.0
), body
data will be encoded in the query string. **This options
object is passed to the native Fetch API method, which means you can use native Fetch API method options as well from Fetch's documentation
To make only a certain request be raw and not parsed by Frisbee:
const res = await api.get('/v1/messages', { raw: false });
Here are a few examples (you can override/merge your set default headers as well per request):
-
To turn off caching, pass cache: 'reload'
to native fetch options:
const res = await api.get('/v1/messages', { cache: 'reload' });
-
To set a custom header value of X-Reply-To
on a POST
request:
const res = await api.post('/messages', {
headers: {
'X-Reply-To': '7s9inuna748y4l1azchi'
}
});
-
raw
optional - will override a global raw
option if set, and if it is true
it will return a raw fetch
response (new as of v2.0.4+)
-
List of available HTTP methods:
api.get(path, options)
- GETapi.head(path, options)
- HEAD (does not currently work - see tests)api.post(path, options)
- POSTapi.put(path, options)
- PUTapi.del(path, options)
- DELETEapi.delete(path, options)
- DELETEapi.options(path, options)
- OPTIONS (does not currently work - see tests)api.patch(path, options)
- PATCH
-
Note that you can chain the auth
method and a HTTP method together:
const res = await api.auth('foo:bar').get('/');
-
interceptor
- object that can be used to manipulate request and response interceptors. It has the following methods:
-
api.interceptor.register(interceptor)
:
Accepts an interceptor object that can have one or more of the following functions
{
request: function (path, options) {
return [path, options];
},
requestError: function (err) {
return Promise.reject(err);
},
response: function (response) {
return response;
},
responseError: function (err) {
return Promise.reject(err);
}
the register
method returns an unregister()
function so that you can unregister the added interceptor.
-
api.interceptor.unregister(interceptor)
:
Accepts the interceptor reference that you want to delete.
-
api.interceptor.clear()
:
Removes all the added interceptors.
-
Note that when interceptors are added in the order ONE->TWO->THREE:
- The
request
/requestError
functions will run in the same order ONE->TWO->THREE
. - The
response
/responseError
functions will run in reversed order THREE->TWO->ONE
.
Logging and Debugging
We highly recommend to use CabinJS as your Node.js and JavaScript logging utility (see Automatic Request Logging for complete examples).
Logging Requests and Responses
You can log both requests and/or responses made to fetch internally in Frisbee. Simply pass a logRequest
and/or logResponse
function.
logRequest
accepts two arguments path
(String) and opts
(Object) and these two arguments are what we call fetch
with internally (e.g. fetch(path, opts)
):
const cabin = require('cabin');
const frisbee = require('frisbee');
const pino = require('pino')({
customLevels: {
log: 30
},
hooks: {
logMethod(inputArgs, method) {
return method.call(this, {
msg: inputArgs[0],
meta: inputArgs[1]
});
}
}
});
const logger = new Cabin({
axe: { logger: pino }
});
const api = new Frisbee({
logRequest: (path, opts) => {
logger.info('fetch request', { path, opts });
}
});
logResponse
accepts three arguments, the first two are the same as logRequest
(e.g. path
and opts
), but the third argument is response
(Object) and is the raw response object returned from fetch (e.g. const response = await fetch(path, opts)
):
const cabin = require('cabin');
const frisbee = require('frisbee');
const pino = require('pino')({
customLevels: {
log: 30
}
});
const logger = new Cabin({
axe: { logger: pino }
});
const api = new Frisbee({
logResponse: (path, opts, res) => {
logger.info('fetch response', { path, opts, res });
}
});
Debug Statements
You can run your application with DEBUG=frisbee node app.js
to output debug logging statements with Frisbee.
Common Issues
Required Features
This list is sourced from ESLint output and polyfilled settings through eslint-plugin-compat.
- Array.from() is not supported in IE 11
- Object.getOwnPropertyDescriptors() is not supported in IE 11
- Object.getOwnPropertySymbols() is not supported in IE 11
- Promise is not supported in Opera Mini all, IE Mobile 11, IE 11
- Promise.race() is not supported in Opera Mini all, IE Mobile 11, IE 11
- Promise.reject() is not supported in Opera Mini all, IE Mobile 11, IE 11
- Promise.resolve() is not supported in Opera Mini all, IE Mobile 11, IE 11
- Reflect is not supported in IE 11
- Symbol.for() is not supported in IE 11
- Symbol.iterator() is not supported in IE 11
- Symbol.prototype() is not supported in IE 11
- Symbol.species() is not supported in IE 11
- Symbol.toPrimitive() is not supported in IE 11
- Symbol.toStringTag() is not supported in IE 11
- Uint8Array is not supported in IE Mobile 11
Frequently Asked Questions
Simply set its value to null
, ''
, or undefined
– and it will be unset and removed from the headers sent with your request.
A common use case for this is when you are attempting to use FormData
and need the content boundary automatically added.
Why do my form uploads randomly fail with React Native
This is due to a bug with setting the boundary. For more information and temporary workaround if you are affected please see facebook/react-native#7564 (comment).
Does this support callbacks, promises, or both
As of version 1.0.0
we have dropped support for callbacks, it now only supports Promises.
What is the fetch
method
It is a WHATWG browser API specification. You can read more about at the following links:
Does the Browser or Node.js support fetch
yet
Yes, a lot of browsers are now supporting it! See this reference for more information http://caniuse.com/#feat=fetch.
If my engine does not support fetch
yet, is there a polyfill
Yes you can use the fetch
method (polyfill) from whatwg-fetch or node-fetch.
By default, React Native already has a built-in fetch
out of the box!
Can I make fetch
support older browsers
Yes, but you'll need a promise polyfill for older browsers.
What is this project about
Use this package as a universal API wrapper for integrating your API in your client-side or server-side projects.
It's a better working alternative (and with less headaches; at least for me) – for talking to your API – than superagent and the default fetch Network method provide.
Use it for projects in Node, React, Angular, React Native, ...
It supports and is tested for both client-side usage (e.g. with Bower, Browserify, or Webpack, with whatwg-fetch
) and also server-side (with node-fetch
).
Why not just use superagent
or fetch
See Background for more information.
Want to build an API back-end with Node.js
See Lad as a great starting point, and read this article about building Node.js API's with authentication.
Need help or want to request a feature
File an issue on GitHub and we'll try our best help you out.
Tests
This package is tested to work with whatwg-fetch
and node-fetch
.
This means that it is compatible for both client-side and server-side usage.
Development
- Fork/clone this repository
- Run
npm install
- Run
npm run watch
to watch the src
directory for changes - Make changes in
src
directory - Write unit tests in
/test/
if you add more stuff - Run
npm test
when you're done - Submit a pull request
Background
The docs suggest that you use superagent
with React Native, but in our experience it did not work properly, therefore we went with the next best solution, the Github fetch
API polyfill included with React Native. After having several issues trying to use fetch
and writing our own API wrapper for a project with it (and running into roadblocks along the way) – we decided to publish this.
Here were the issues we discovered/filed related to this:
We know that solutions like superagent
exist, but they don't seem to work well with React Native (which was our use case for this package).
In addition, the authors of WHATWG's fetch API only support throwing errors instead of catching them and bubbling them up to the callback/promise (for example, with Frisbee any HTTP or API errors are found in the res.err
object).
Therefore we created frisbee
to serve as our API glue, and hopefully it'll serve as yours too.
Contributors
Name | Website |
---|
Nick Baugh | http://niftylettuce.com/ |
Alexis Tyler | |
Assem-Hafez | |
Jordan Denison | |
James | |
Sampsa Saarela | |
Julien Moutte | |
Charles Soetan | |
Kesha Antonov | |
Ben Turley | |
Richard Evans | |
Hawken Rives | |
Fernando Montoya | |
Brent Vatne | |
Hosmel Quintana | |
Kyle Kirbatski | |
Adam Jenkins | |
Credits
License
MIT © Nick Baugh